'use strict';

const fs = require('fs');
const path = require('path');

const inquirer = require('inquirer');
const fsExtra = require('fs-extra');
const paramCase = require('param-case');
const ora = require('ora');
const ejs = require('ejs');
const glob = require('glob');

const Command = require('@x3-cli-dev/command');
const log = require('@x3-cli-dev/log');
const request = require('@x3-cli-dev/request');
const Package = require('@x3-cli-dev/package');
const { sleep, execAsync } = require('@x3-cli-dev/utils');

const TYPE_PROJECT = 'project';
const TYPE_LIB = 'library';

const CACHE_DIR = 'templates';
const WHITE_COMMAND = ['npm', 'cnpm'];
class InitCommand extends Command {
  init() {
    this.projectName = this._argv[0] || '';
    this.force = !!this._options.force;
    this.skipInstall = !!this._options.skipInstall;
    log.verbose('projectName', this.projectName);
    log.verbose('force', this.force);
    log.verbose('skipInstall', this.skipInstall);
  }

  async exec() {
    try {
      const projectInfo = await this.prepare();
      if (projectInfo) {
        this.projectInfo = projectInfo;
        log.verbose('projectInfo', projectInfo);

        await this.downloadTemplate();
        await this.installTemplate();

        const ignore = ['**/node_modules/**', ...this.projectInfo.ignore];
        await this.ejsRender({ ignore });

        if (!this.skipInstall) {
          await this.installDependencies();
        }
      }
    } catch (error) {
      log.error(error.message);
    }
  }

  async prepare() {
    const template = await request.getTemplateList();
    if (!template || template.length < 0) {
      throw new Error(`不存在项目模板`);
    }
    this.template = template;

    const localPath = process.cwd();
    // 判断路径是否为空
    if (!this.isDirEmpty(localPath)) {
      let ifContinue = false;
      if (!this.force) {
        // 是否继续创建
        ifContinue = (
          await inquirer.prompt({
            name: 'ifContinue',
            type: 'confirm',
            message: '当前文件夹不为空，是否继续创建？',
            default: false,
          })
        ).ifContinue;
        if (!ifContinue) return;
      }

      // 是否强制删除当前目录
      if (ifContinue || this.force) {
        const { ifDelete } = await inquirer.prompt({
          name: 'ifDelete',
          type: 'confirm',
          message: '是否确认清空当前目录下的文件？',
          default: false,
        });

        if (!ifDelete) return;
        fsExtra.emptyDirSync(localPath);
      }
    }
    return this.getProjectInfo();
  }

  async getProjectInfo() {
    const { type } = await inquirer.prompt({
      name: 'type',
      type: 'list',
      choices: [
        { name: '项目', value: TYPE_PROJECT },
        { name: '组件库', value: TYPE_LIB },
      ],
      default: TYPE_PROJECT,
      message: '请选择初始化项目类型',
    });

    const { template } = await inquirer.prompt({
      name: 'template',
      type: 'list',
      choices: this.getTemplateChoices(type),
      message: '请选择项目模板',
    });
    this.templateInfo = template;

    let dependencies = [];
    if (this.templateInfo.dependencies && this.templateInfo.dependencies.length > 0) {
      dependencies = (
        await inquirer.prompt({
          name: 'dependencies',
          type: 'checkbox',
          choices: this.getDependenciesChoices(),
          message: '请选择安装依赖',
        })
      ).dependencies;
      log.verbose('dependencies', dependencies);
    }

    return {
      ...this.templateInfo,
      projectName: this.projectName,
      className: paramCase(this.projectName),
      dependencies,
    };
  }

  getTemplateChoices(type) {
    return this.template.filter(item => item.type === type).map(item => ({ name: item.projectName, value: item }));
  }

  getDependenciesChoices() {
    return this.templateInfo.dependencies.map(dependency => ({
      name: dependency.npmName,
      value: dependency,
    }));
  }

  async downloadTemplate() {
    const homePath = process.env.X3_CLI_HOME_PATH;
    const targetPath = path.resolve(homePath, CACHE_DIR);
    const storeDir = path.resolve(targetPath, 'node_modules');
    log.verbose('targetPath', targetPath);
    log.verbose('storeDir', storeDir);
    const pkg = new Package({
      targetPath,
      storeDir,
      packageVersion: this.projectInfo.version,
      packageName: this.projectInfo.npmName,
    });

    const spinner = ora('').start();

    try {
      if (await pkg.exist()) {
        spinner.text = '正在更新模板...';
        await pkg.update();
        await sleep();
        spinner.succeed('更新模板成功');
      } else {
        spinner.text = '正在下载模板...';
        await pkg.install();
        await sleep();
        spinner.succeed('下载模板成功');
      }
    } catch (error) {
      log.verbose(error.message);
    }

    this.package = pkg;
  }

  async installTemplate() {
    log.verbose('package', this.package);
    // 拷贝模板
    const spinner = ora('开始安装模板').start();
    const templatePath = path.resolve(this.package.cacheFilePath, 'template');
    const targetPath = path.resolve(process.cwd());
    try {
      sleep();
      fsExtra.ensureDirSync(templatePath);
      fsExtra.ensureDirSync(targetPath);
      fsExtra.copySync(templatePath, targetPath);
      spinner.succeed('安装模板成功');
    } catch (error) {
      spinner.fail('安装模板失败');
      throw error;
    }
  }

  async ejsRender(options) {
    const targetPath = process.cwd();
    return new Promise((resolve, reject) => {
      glob('**', { cwd: targetPath, nodir: true, ignore: options.ignore }, (error, files) => {
        log.verbose('files', files);

        if (error) {
          reject(error);
        }

        Promise.all(
          files.map(file => {
            return new Promise((res, rej) => {
              const filePath = path.join(targetPath, file);
              ejs.renderFile(filePath, this.projectInfo, {}, (err, str) => {
                if (err) {
                  rej(err);
                } else {
                  fsExtra.writeFileSync(filePath, str);
                  res(str);
                }
              });
            });
          }),
        )
          .then(() => {
            resolve();
          })
          .catch(err => {
            reject(err);
          });
      });
    });
  }

  async installDependencies() {
    const { installCommand } = this.projectInfo;
    return this.execCommand(installCommand, '安装依赖失败');
  }

  isDirEmpty(path) {
    const files = fs.readdirSync(path);
    const except = ['node_modules'];
    return files.every(file => file.startsWith('.') || except.includes(file));
  }

  checkCommand(cmd) {
    if (WHITE_COMMAND.includes(cmd)) {
      return cmd;
    }
    return null;
  }

  async execCommand(command, errMsg) {
    let ret;
    if (command) {
      const cmdArray = command.split(' ');
      const cmd = this.checkCommand(cmdArray[0]);
      if (!cmd) {
        throw new Error('命令不存在！命令：' + command);
      }
      const args = cmdArray.slice(1);
      ret = await execAsync(cmd, args, {
        stdio: 'inherit',
        cwd: process.cwd(),
      });
    }
    if (ret !== 0) {
      throw new Error(errMsg);
    }
    return ret;
  }
}

function init(argv) {
  return new InitCommand(argv);
}

module.exports = init;
module.exports.InitCommand = InitCommand;
